package com.tinyspring.context;

import com.tinyspring.advisor.AdviceType;
import com.tinyspring.advisor.Advisor;
import com.tinyspring.annotation.*;
import com.tinyspring.factory.AopProxyFactory;
import com.tinyspring.factory.BeanFactory;
import com.tinyspring.support.BeanDefinition;
import com.tinyspring.support.BeanDefinitionRegistry;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: TianYuan
 * @description: Spring容器上下文
 * @version: 1.0
 */
public class ApplicationContext implements BeanFactory, BeanDefinitionRegistry {

    // 存储Bean定义
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    // 存储单例Bean实例
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 存储所有Bean名称
    private final List<String> beanNames = new ArrayList<>();

    // 存储切面信息
    private final Map<String, Object> aspectBeans = new ConcurrentHashMap<>();

    // 存储切面通知信息
    private final List<Advisor> advisors = new ArrayList<>();

    public ApplicationContext(String basePackage) {
        //扫描指定包路径下的组件
        scanBasePackage(basePackage);
        //初始化所有单例Bean
        initializeBeans();
        //识别切面类
        identifyAspects();
        //创建切面通知器
        createAdvisors();
        //对Bean进行AOP处理
        applyAspects();
    }

    /**
     * 对Bean进行AOP处理
     */
    private void applyAspects(){
        for (String beanName:new ArrayList<>(singletonObjects.keySet())){
            Object bean = singletonObjects.get(beanName);
            Class<?> targetClass = bean.getClass();

            List<Advisor> matchedAdvisors = new ArrayList<>();
            //查询是否有匹配的切面
            for (Method method:targetClass.getMethods()){
                for(Advisor advisor:advisors){
                    //这里的切面用简单的方式实现 例如 com.tinyspring.service.impl.TestServiceImpl.test()
                    if (advisor.getPointcutExpression().equals(targetClass.getName()+"."+method.getName()+"()")){
                        matchedAdvisors.add(advisor);
                    }
                }
            }

            if (!matchedAdvisors.isEmpty()){
                //创建代理对象替换原对象
                Object proxy = AopProxyFactory.createProxy(bean, matchedAdvisors);
                singletonObjects.put(beanName,proxy);
            }
        }

        // 重新依赖注入，解决原来注入的对象被替换成了代理对象
        for (Object bean : singletonObjects.values()) {
            injectDependencies(bean);
        }
    }

    /**
     * 创建切面通知器
     */
    private void createAdvisors(){
        for (Map.Entry<String,Object> entry:aspectBeans.entrySet()){
            Object aspectBean = entry.getValue();
            Class<?> aspectClass = aspectBean.getClass();

            for (Method method:aspectClass.getMethods()){
                if (method.isAnnotationPresent(Before.class)){
                    Before before = method.getAnnotation(Before.class);
                    Advisor advisor = new Advisor();
                    advisor.setAspectBean(aspectBean);
                    advisor.setAdviceMethod(method);
                    advisor.setPointcutExpression(before.value());
                    advisor.setAdviceType(AdviceType.BEFORE);
                    advisors.add(advisor);
                }else if (method.isAnnotationPresent(After.class)){
                    After after = method.getAnnotation(After.class);
                    Advisor advisor = new Advisor();
                    advisor.setAspectBean(aspectBean);
                    advisor.setAdviceMethod(method);
                    advisor.setPointcutExpression(after.value());
                    advisor.setAdviceType(AdviceType.AFTER);
                    advisors.add(advisor);
                }else if (method.isAnnotationPresent(Around.class)){
                    Around around = method.getAnnotation(Around.class);
                    Advisor advisor = new Advisor();
                    advisor.setAspectBean(aspectBean);
                    advisor.setAdviceMethod(method);
                    advisor.setPointcutExpression(around.value());
                    advisor.setAdviceType(AdviceType.AROUND);
                    advisors.add(advisor);
                }
            }
        }
    }

    /**
     * 识别所有的切面类
     */
    private void identifyAspects(){
        for (String key:beanNames){
            Object bean = singletonObjects.get(key);
            if (bean != null && bean.getClass().isAnnotationPresent(Aspect.class)){
                aspectBeans.put(key,bean);
            }
        }
    }

    /**
     * 扫描基础包，注册Bean定义
     * @param basePackage
     */
    private void scanBasePackage(String basePackage) {
        String path = basePackage.replace('.', '/');
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL resource = classLoader.getResource(path);

        if (resource != null) {
            File directory = new File(resource.getFile());
            if (directory.exists()) {
                File[] files = directory.listFiles();
                if (files != null) {
                    for (File file : files) {
                        if (file.isDirectory()) {
                            // 递归扫描子包
                            scanBasePackage(basePackage + "." + file.getName());
                        } else if (file.getName().endsWith(".class")) {
                            String className = basePackage + "." + file.getName().replace(".class", "");
                            try {
                                Class<?> clazz = Class.forName(className);
                                if (clazz.isAnnotationPresent(Component.class)) {
                                    // 注册Bean定义
                                    Component component = clazz.getAnnotation(Component.class);
                                    String beanName = component.value();
                                    if (beanName.isEmpty()) {
                                        beanName = clazz.getSimpleName();
                                        beanName = Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1);
                                    }

                                    BeanDefinition beanDefinition = new BeanDefinition(clazz);
                                    registerBeanDefinition(beanName, beanDefinition);
                                    beanNames.add(beanName);
                                }
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 初始化所有单例Bean
     */
    private void initializeBeans() {
        for (String beanName : beanNames) {
            BeanDefinition beanDefinition = getBeanDefinition(beanName);
            if ("singleton".equals(beanDefinition.getScope())) {
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
        // 依赖注入
        for (Object bean : singletonObjects.values()) {
            injectDependencies(bean);
        }
    }

    /**
     * 依赖注入
     * @param bean
     */
    private void injectDependencies(Object bean) {
        Class<?> clazz = bean.getClass();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(Autowired.class)) {
                //按照类型匹配
                Object dependency = getBean(field.getType());
                if (dependency != null) {
                    field.setAccessible(true);
                    try {
                        field.set(bean, dependency);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException("依赖注入失败: " + field.getName(), e);
                    }
                }
            }
        }
    }

    /**
     * 创建bean实例
     * @param beanName
     * @param beanDefinition
     * @return
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        try {
            return beanDefinition.getBeanClass().newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("创建Bean实例失败: " + beanName, e);
        }
    }


    @Override
    public Object getBean(String name) {
        return singletonObjects.get(name);
    }

    @Override
    public <T> T getBean(Class<T> requiredType) {
        for (Object bean : singletonObjects.values()) {
            if (requiredType.isInstance(bean)) {
                return requiredType.cast(bean);
            }
        }
        return null;
    }

    @Override
    public boolean containsBean(String name) {
        return singletonObjects.containsKey(name);
    }

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }
}
